//
//  Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//

#import "SignalAccount.h"
#import "Contact.h"
#import "ContactsManagerProtocol.h"
#import "NSData+Image.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import "UIImage+OWS.h"
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSString+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>

NS_ASSUME_NONNULL_BEGIN

NSUInteger const SignalAccountSchemaVersion = 1;

/* We need to query the system preferences to achieve the behaviour at Messages on iOS.
 
 If we ask NSPersonNameComponentsFormatter for "short" we will get the nickname if it exists but if it _doesn't_ exit we'll just get the first name. (Or the name pattern the user has selected in their system preferences. This means that in the conversation list in the left, where Messages displays the full name of a contact if they don't have a nickname, we'd just display the Short Name. To match the behaviour we ask UserDefaults for the value of this key and prefer to use the nickname, if available, in the conversation list.
*/
static NSString *kSignalPreferNicknamesPreference = @"NSPersonNameDefaultShouldPreferNicknamesPreference";

@interface SignalAccount ()

@property (nonatomic, readonly) NSUInteger accountSchemaVersion;

@property (nonatomic) NSString *multipleAccountLabelText;

@property (nonatomic, nullable) Contact *contact;

@end

#pragma mark -

@implementation SignalAccount

#pragma mark - Dependencies

- (id<ContactsManagerProtocol>)contactsManager
{
    return SSKEnvironment.shared.contactsManager;
}

- (SignalAccountReadCache *)signalAccountReadCache
{
    return SSKEnvironment.shared.modelReadCaches.signalAccountReadCache;
}

#pragma mark -

+ (BOOL)shouldBeIndexedForFTS
{
    return YES;
}

- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient
                                contact:(nullable Contact *)contact
               multipleAccountLabelText:(nullable NSString *)multipleAccountLabelText
{
    OWSAssertDebug(signalRecipient);
    return [self initWithSignalServiceAddress:signalRecipient.address
                                      contact:contact
                     multipleAccountLabelText:multipleAccountLabelText];
}

- (instancetype)initWithSignalServiceAddress:(SignalServiceAddress *)serviceAddress
{
    return [self initWithSignalServiceAddress:serviceAddress contact:nil multipleAccountLabelText:nil];
}

- (instancetype)initWithSignalServiceAddress:(SignalServiceAddress *)serviceAddress
                                     contact:(nullable Contact *)contact
                    multipleAccountLabelText:(nullable NSString *)multipleAccountLabelText
{
    OWSAssertDebug(serviceAddress.isValid);
    if (self = [super init]) {
        _recipientUUID = serviceAddress.uuidString;
        _recipientPhoneNumber = serviceAddress.phoneNumber;
        _accountSchemaVersion = SignalAccountSchemaVersion;
        _contact = contact;
        _multipleAccountLabelText = multipleAccountLabelText;
    }
    return self;
}

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (!self) {
        return self;
    }

    // Migrating from an everyone has a phone number world to a
    // world in which we have UUIDs
    if (_accountSchemaVersion == 0) {
        // Rename recipientId to recipientPhoneNumber
        _recipientPhoneNumber = [coder decodeObjectForKey:@"recipientId"];

        OWSAssert(_recipientPhoneNumber != nil);
    }

    _accountSchemaVersion = SignalAccountSchemaVersion;

    return self;
}

- (instancetype)initWithContact:(nullable Contact *)contact
              contactAvatarHash:(nullable NSData *)contactAvatarHash
          contactAvatarJpegData:(nullable NSData *)contactAvatarJpegData
       multipleAccountLabelText:(NSString *)multipleAccountLabelText
           recipientPhoneNumber:(nullable NSString *)recipientPhoneNumber
                  recipientUUID:(nullable NSString *)recipientUUID
{
    self = [super init];
    if (!self) {
        return self;
    }

    OWSAssertDebug(recipientPhoneNumber != nil || recipientUUID != nil);

    _contact = contact;
    _contactAvatarHash = contactAvatarHash;
    _contactAvatarJpegData = contactAvatarJpegData;
    _multipleAccountLabelText = multipleAccountLabelText;
    _recipientPhoneNumber = recipientPhoneNumber;
    _recipientUUID = recipientUUID;
    _accountSchemaVersion = SignalAccountSchemaVersion;

    return self;
}

// --- CODE GENERATION MARKER

// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run `sds_codegen.sh`.

// clang-format off

- (instancetype)initWithGrdbId:(int64_t)grdbId
                      uniqueId:(NSString *)uniqueId
                         contact:(nullable Contact *)contact
               contactAvatarHash:(nullable NSData *)contactAvatarHash
           contactAvatarJpegData:(nullable NSData *)contactAvatarJpegData
        multipleAccountLabelText:(NSString *)multipleAccountLabelText
            recipientPhoneNumber:(nullable NSString *)recipientPhoneNumber
                   recipientUUID:(nullable NSString *)recipientUUID
{
    self = [super initWithGrdbId:grdbId
                        uniqueId:uniqueId];

    if (!self) {
        return self;
    }

    _contact = contact;
    _contactAvatarHash = contactAvatarHash;
    _contactAvatarJpegData = contactAvatarJpegData;
    _multipleAccountLabelText = multipleAccountLabelText;
    _recipientPhoneNumber = recipientPhoneNumber;
    _recipientUUID = recipientUUID;

    return self;
}

// clang-format on

// --- CODE GENERATION MARKER

- (nullable NSString *)contactPreferredDisplayName {
    NSPersonNameComponents *components = [NSPersonNameComponents new];
    components.givenName = self.contact.firstName;
    components.familyName = self.contact.lastName;
    components.nickname = self.contact.nickname;
    
    NSString *result = nil;
    // If we have a nickname check what the user prefers.
    if (components.nickname.length && [[NSUserDefaults standardUserDefaults] boolForKey: kSignalPreferNicknamesPreference]) {
        result = components.nickname;
    } else {
        result = [NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents: components
                                                                                    style: NSPersonNameComponentsFormatterStyleDefault
                                                                                  options: 0];
    }
    return result.filterStringForDisplay;
}

- (nullable NSString *)contactFullName
{
    return self.contact.fullName.filterStringForDisplay;
}

- (nullable NSString *)contactFirstName
{
    return self.contact.firstName.filterStringForDisplay;
}

- (nullable NSString *)contactLastName
{
    return self.contact.lastName.filterStringForDisplay;
}

- (NSString *)multipleAccountLabelText
{
    NSString *_Nullable result = _multipleAccountLabelText.filterStringForDisplay;
    return result != nil ? result : @"";
}

- (SignalServiceAddress *)recipientAddress
{
    return [[SignalServiceAddress alloc] initWithUuidString:self.recipientUUID phoneNumber:self.recipientPhoneNumber];
}

- (BOOL)hasSameContent:(SignalAccount *)other
{
    OWSAssertDebug(other != nil);

    // NOTE: We don't want to compare contactAvatarJpegData.
    //       It can't change without contactAvatarHash changing
    //       as well.
    return ([NSObject isNullableObject:self.recipientPhoneNumber equalTo:other.recipientPhoneNumber] &&
        [NSObject isNullableObject:self.recipientUUID equalTo:other.recipientUUID] &&
        [NSObject isNullableObject:self.contact equalTo:other.contact] &&
        [NSObject isNullableObject:self.multipleAccountLabelText equalTo:other.multipleAccountLabelText] &&
        [NSObject isNullableObject:self.contactAvatarHash equalTo:other.contactAvatarHash]);
}

- (void)tryToCacheContactAvatarData
{
    OWSAssertDebug(self.contactAvatarHash == nil);
    OWSAssertDebug(self.contactAvatarJpegData == nil);

    if (self.contact == nil) {
        OWSFailDebug(@"Missing contact.");
        return;
    }

    if (self.contact.isFromContactSync) {
        OWSLogVerbose(@"not caching data for synced contact");
        return;
    }

    OWSAssertDebug(self.contact.cnContactId);
    NSData *_Nullable contactAvatarData = [self.contactsManager avatarDataForCNContactId:self.contact.cnContactId];
    if (contactAvatarData == nil) {
        return;
    }
    _contactAvatarHash = [Cryptography computeSHA256Digest:contactAvatarData];
    OWSAssertDebug(self.contactAvatarHash != nil);
    if (self.contactAvatarHash == nil) {
        return;
    }

    _contactAvatarJpegData = [UIImage validJpegDataFromAvatarData:contactAvatarData];
    if (self.contactAvatarJpegData == nil) {
        OWSFailDebug(@"Could not convert avatar to JPEG.");
        return;
    }
}

- (void)anyDidInsertWithTransaction:(SDSAnyWriteTransaction *)transaction
{
    [super anyDidInsertWithTransaction:transaction];

    [self.signalAccountReadCache didInsertOrUpdateSignalAccount:self transaction:transaction];
}

- (void)anyDidUpdateWithTransaction:(SDSAnyWriteTransaction *)transaction
{
    [super anyDidUpdateWithTransaction:transaction];

    [self.signalAccountReadCache didInsertOrUpdateSignalAccount:self transaction:transaction];
}

- (void)anyDidRemoveWithTransaction:(SDSAnyWriteTransaction *)transaction
{
    [super anyDidRemoveWithTransaction:transaction];

    [self.signalAccountReadCache didRemoveSignalAccount:self transaction:transaction];
}

- (void)updateWithContact:(nullable Contact *)contact transaction:(SDSAnyWriteTransaction *)transaction
{
    [self anyUpdateWithTransaction:transaction
                             block:^(SignalAccount *account) {
                                 account.contact = contact;
                             }];
}

#if TESTABLE_BUILD
- (void)replaceContactForTests:(nullable Contact *)contact
{
    self.contact = contact;
}
#endif

@end

NS_ASSUME_NONNULL_END
